﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine;

namespace PostscreenEffects
{
    class ChainRenderTargets
    {
        private static int MAX_DOWNSAMPLE = 6;
        private static float SCALE_FACTOR = 2.0f;
        private static String BLUR_QUALITY = "Quality3x3";

        private RenderTarget2D _RenderTarget;
        private RenderTarget2D[] _DownSampleTargets;
        private RenderTarget2D[] _UpSampleTargets;
        private RenderTarget2D[] _TempTargets;
        private GraphicsDevice _Device;
        private int _Width;
        private int _Height;
        private Effect _LinearFilterEffect;
        private Effect _GaussianFilterEffect;
        private Effect _AdditiveBlendEffect;
        private SpriteBatch _SpriteBatch;

        // ---------------------------------------------------------
        public ChainRenderTargets(
                    RenderTarget2D RenderTarget)
        {

            _SpriteBatch = EngineStuff.SpriteBatch;
            _LinearFilterEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\LinearFilter");
            _GaussianFilterEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\gaussianMix");
            _AdditiveBlendEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\Blend");
            _Device = EngineStuff.GraphicsDevice;
            _Width = RenderTarget.Width;
            _Height = RenderTarget.Height;
            _RenderTarget = RenderTarget;
            _DownSampleTargets = new RenderTarget2D[MAX_DOWNSAMPLE];
            _TempTargets = new RenderTarget2D[MAX_DOWNSAMPLE];
            _UpSampleTargets = new RenderTarget2D[MAX_DOWNSAMPLE];

            PrepareMipMapLevels();
        }

        public ChainRenderTargets(                                  
            RenderTarget2D RenderTarget,
            int Width,
            int Height)
        {
            
            _SpriteBatch = EngineStuff.SpriteBatch;
            _LinearFilterEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\LinearFilter");
            _GaussianFilterEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\gaussianMix");
            _AdditiveBlendEffect  = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\Blend");            
            _Device = EngineStuff.GraphicsDevice;
            _Width = Width;
            _Height = Height;
            _RenderTarget = RenderTarget;
            _DownSampleTargets = new RenderTarget2D[MAX_DOWNSAMPLE];
            _TempTargets = new RenderTarget2D[MAX_DOWNSAMPLE];
            _UpSampleTargets = new RenderTarget2D[MAX_DOWNSAMPLE];

            PrepareMipMapLevels();
        }
        // ---------------------------------------------------------
        public void PrepareMipMapLevels()
        {
            int Factor = 2;
            for (int i = 0; i < MAX_DOWNSAMPLE; ++i)
            {
                _DownSampleTargets[i] = new RenderTarget2D(
                    _Device,
                    _Width / Factor,
                    _Height / Factor,
                    1,
                    SurfaceFormat.HalfVector4);

                _TempTargets[i] = new RenderTarget2D(
                    _Device,
                    _Width / Factor,
                    _Height / Factor,
                    1,
                    SurfaceFormat.HalfVector4);

                if (i < MAX_DOWNSAMPLE - 1)
                {
                    _UpSampleTargets[i + 1] = new RenderTarget2D(
                        _Device,
                        _Width / Factor,
                        _Height / Factor,
                        1,
                        SurfaceFormat.HalfVector4);
                } // if

                Factor *= 2;
            } // for

            _UpSampleTargets[0] = new RenderTarget2D(
                _Device,
                _Width,
                _Height,
                1,
                SurfaceFormat.HalfVector4);
        }
        // ---------------------------------------------------------
        public void GenerateMipMapLevels()
        {
            Vector2 TextureSize = new Vector2(_Width, _Height);
            Rectangle Rect = new Rectangle(
                0,
                0,
                (int)_Width,
                (int)_Height);

            Effect effect = _LinearFilterEffect;
            Texture2D CurrentTex = _RenderTarget.GetTexture();
            for (int i = 0; i < MAX_DOWNSAMPLE; ++i)
            {
                TextureSize /= 2f;
                Rect.Width /= 2;
                Rect.Height /= 2;

                _Device.SetRenderTarget(0, _DownSampleTargets[i]);
                _Device.Clear(ClearOptions.Target, Vector4.Zero, 1, 0);
                _Device.Textures[0] = CurrentTex;
                effect.CurrentTechnique = effect.Techniques[0];
                effect.Parameters["gTextureSize"].SetValue(TextureSize);
                effect.Begin();
                EffectPass pass = effect.CurrentTechnique.Passes[0];
                _SpriteBatch.Begin(
                    SpriteBlendMode.None,
                    SpriteSortMode.Immediate,
                    SaveStateMode.SaveState);
                pass.Begin();
                _SpriteBatch.Draw(CurrentTex, Rect, Color.White);
                pass.End();
                _SpriteBatch.End();
                effect.End();

                _Device.SetRenderTarget(0, null);
                CurrentTex = _DownSampleTargets[i].GetTexture();
            } // for 
            _Device.SetRenderTarget(0, null);
        }
        
        public void AdditiveBlend()
        {            
            // 1/64x1/64->1/32x1/32
            _Device.SetRenderTarget(0, _UpSampleTargets[5]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            _SpriteBatch.Draw(
                _DownSampleTargets[5].GetTexture(),
                new Rectangle(0, 0, _Width / 32, _Height / 32),
                Color.White);
            _SpriteBatch.Draw(
                _DownSampleTargets[4].GetTexture(),
                new Rectangle(0, 0, _Width / 32, _Height / 32),
                Color.White);
            _SpriteBatch.End();

            // 1/32x1/32->1/16x1/16
            _Device.SetRenderTarget(0, _UpSampleTargets[4]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            _SpriteBatch.Draw(
                _UpSampleTargets[5].GetTexture(),
                new Rectangle(0, 0, _Width / 16, _Height / 16),
                Color.White);
            _SpriteBatch.Draw(
                _DownSampleTargets[3].GetTexture(),
                new Rectangle(0, 0, _Width / 16, _Height / 16),
                Color.White);
            _SpriteBatch.End();

            // 1/16x1/16->1/8x1/8
            _Device.SetRenderTarget(0, _UpSampleTargets[3]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            _SpriteBatch.Draw(
                _UpSampleTargets[4].GetTexture(),
                new Rectangle(0, 0, _Width / 8, _Height / 8),
                Color.White);
            _SpriteBatch.Draw(
                _DownSampleTargets[2].GetTexture(),
                new Rectangle(0, 0, _Width / 8, _Height / 8),
                Color.White);
            _SpriteBatch.End();

            // 1/8x1/8->1/4x1/4
            _Device.SetRenderTarget(0, _UpSampleTargets[2]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            _SpriteBatch.Draw(
                _UpSampleTargets[3].GetTexture(),
                new Rectangle(0, 0, _Width / 4, _Height / 4),
                Color.White);
            _SpriteBatch.Draw(
                _DownSampleTargets[1].GetTexture(),
                new Rectangle(0, 0, _Width / 4, _Height / 4),
                Color.White);
            _SpriteBatch.End();

            // 1/4x1/4->1/2x1/2
            _Device.SetRenderTarget(0, _UpSampleTargets[1]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin(
                SpriteBlendMode.Additive,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            _SpriteBatch.Draw(
                _UpSampleTargets[2].GetTexture(),
                new Rectangle(0, 0, _Width / 2, _Height / 2),
                Color.White);
            _SpriteBatch.Draw(
                _DownSampleTargets[0].GetTexture(),
                new Rectangle(0, 0, _Width / 2, _Height / 2),
                Color.White);
            _SpriteBatch.End();

            // 1/2x1/2->1x1
            _Device.SetRenderTarget(0, _UpSampleTargets[0]);
            _Device.Clear(ClearOptions.Target, Vector4.Zero, 1, 0);
            _Device.Textures[0] = _UpSampleTargets[1].GetTexture();
            _AdditiveBlendEffect.CurrentTechnique = _AdditiveBlendEffect.Techniques[0];
            _AdditiveBlendEffect.Parameters["gFactor"].SetValue(1.0f);
            _AdditiveBlendEffect.Begin();
            EffectPass pass = _AdditiveBlendEffect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(
                _UpSampleTargets[1].GetTexture(),
                new Rectangle(0, 0, _Width, _Height),
                Color.White);
            pass.End();
            _SpriteBatch.End();
            _AdditiveBlendEffect.End();
            _Device.SetRenderTarget(0, null);
        }

        
        public void ApplyBlurToAllMipMaps()
        {
            // apply blur to all scales of the original target
            // again, this is done manually and might be done in
            // an array
            // ps: the temporary targets are used, here

            // custom spritebatch vertexshader
            Vector2 Size;

            /*
             * copy Rendertargets to temporary buffers
             */
            _Device.SetRenderTarget(0, _TempTargets[5]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();
            _SpriteBatch.Draw(_DownSampleTargets[5].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            _Device.SetRenderTarget(0, _TempTargets[4]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();   
            _SpriteBatch.Draw(_DownSampleTargets[4].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            _Device.SetRenderTarget(0, _TempTargets[3]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();
            _SpriteBatch.Draw(_DownSampleTargets[3].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            _Device.SetRenderTarget(0, _TempTargets[2]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();
            _SpriteBatch.Draw(_DownSampleTargets[2].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            _Device.SetRenderTarget(0, _TempTargets[1]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();
            _SpriteBatch.Draw(_DownSampleTargets[1].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            _Device.SetRenderTarget(0, _TempTargets[0]);
            _Device.Clear(Color.Black);
            _SpriteBatch.Begin();
            _SpriteBatch.Draw(_DownSampleTargets[0].GetTexture(),
                Vector2.Zero,
                Color.White);
            _SpriteBatch.End();

            /*
             * render temporary buffers to downscaled targets
             */
            Size.X = _Width / 64;
            Size.Y = _Height / 64;

            Effect effect = _GaussianFilterEffect;

            _Device.SetRenderTarget(0, _DownSampleTargets[5]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[5].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            EffectPass pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[5].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            Size.X = _Width / 32;
            Size.Y = _Height / 32;
            _Device.SetRenderTarget(0, _DownSampleTargets[4]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[4].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[4].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            Size.X = _Width / 16;
            Size.Y = _Height / 16;
            _Device.SetRenderTarget(0, _DownSampleTargets[3]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[3].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[3].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            Size.X = _Width / 8;
            Size.Y = _Height / 8;
            _Device.SetRenderTarget(0, _DownSampleTargets[2]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[2].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[2].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            Size.X = _Width / 4;
            Size.Y = _Height / 4;
            _Device.SetRenderTarget(0, _DownSampleTargets[1]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[1].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[1].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            Size.X = _Width / 2;
            Size.Y = _Height / 2;
            _Device.SetRenderTarget(0, _DownSampleTargets[0]);
            _Device.Clear(Color.Black);
            _Device.Textures[0] = _TempTargets[0].GetTexture();
            effect.CurrentTechnique = effect.Techniques[BLUR_QUALITY];
            effect.Parameters["ViewportSize"].SetValue(Size);
            effect.Parameters["TextureSize"].SetValue(Size);
            effect.Parameters["MatrixTransform"].SetValue(Matrix.Identity);
            effect.Parameters["gScreenWidth"].SetValue(Size.X);
            effect.Parameters["gScreenHeight"].SetValue(Size.Y);
            effect.Parameters["gScaleFactor"].SetValue(SCALE_FACTOR);
            effect.Begin();
            pass = effect.CurrentTechnique.Passes[0];
            _SpriteBatch.Begin(
                SpriteBlendMode.None,
                SpriteSortMode.Immediate,
                SaveStateMode.SaveState);
            pass.Begin();
            _SpriteBatch.Draw(_TempTargets[0].GetTexture(),
                Vector2.Zero,
                Color.White);
            pass.End();
            _SpriteBatch.End();
            effect.End();

            _Device.SetRenderTarget(0, null);
        }
        // ---------------------------------------------------------
        public void SaveMipLevels(String Directory, String Prefix)
        {
            for (int i = 0; i < MAX_DOWNSAMPLE; ++i)
            {
                Texture2D tex;
                tex = _DownSampleTargets[i].GetTexture();
                tex.Save(Directory + Prefix + i + ".dds", ImageFileFormat.Dds);
            } // for
        }
        // ---------------------------------------------------------
        public RenderTarget2D GetUpsampleTarget()
        {
            return _UpSampleTargets[0];
            //return _RenderTarget;
        }        
        
        // ---------------------------------------------------------
        public RenderTarget2D GetDownSampleTarget(int MipMapLevel)
        {
            if (MipMapLevel == 0)
            {
                return _RenderTarget;
            }
            else
            {
                return _DownSampleTargets[MipMapLevel - 1];
            }
        }        
        // ---------------------------------------------------------
        public Texture2D GetDownTexture(int MipMapLevel)
        {
            if (MipMapLevel == 0)
            {
                return _RenderTarget.GetTexture();
            }
            else
            {
                return _DownSampleTargets[MipMapLevel - 1].GetTexture();
            }
        }
        // ---------------------------------------------------------
        public RenderTarget2D[] GetMipLevels()
        {
            return _DownSampleTargets;
        }
        // ---------------------------------------------------------
        //public RenderTarget2D[] GetTempLevels()
        //{
        //    return _TempTargets;
        //}
        // ---------------------------------------------------------
    }
}